package com.urbanflow.vinci.controler;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import com.urbanflow.vinci.gui.Horodatage;
import com.urbanflow.vinci.main.Arret;
import com.urbanflow.vinci.main.Incident;

public class DialogManager {
	
	//L'identifiant unique du bot � faire bouger.
	private String botToken;
	//Le "mot de passe" du bot � faire bouger.
	private String botSecret;
	//L'identifiant unique de la session de jeu.
	private String gameToken;
	
	//Erreur g�n�rale sur l'objet (erreur du programme), avec sa variable string assortie pour renseigner un peu plus en d�tail.
	public Boolean Erreur_Detectee = false;
	public String Erreur = "";
	
	//Check
	public int TimeRemainingBeforeGameStart = 0;
	public Boolean GameStarted = false;
	public String IdArretDepart = null;
	public String IdArretDestination = null;
	public Date DateDepart = null;
		
	//Move
	//Erreure g�n�rale sur move. Si True, alors un des bool�ens ci-dessous est true aussi.
	public Boolean ErreurMove = false;
	public Boolean LigneInconnue = false;
	public Boolean ArretPasSurLigne = false;
	public Boolean LigneNonAccessible = false;
	public Boolean ErreurLigne = false;
	public Boolean HoraireMalFormate = false;
	public Boolean HoraireDepasse = false;
	public Boolean HoraireInconnu = false;
	public Boolean Rerouted = false;	
	
	//Hip Hip Hip ! 
	public Boolean VeniVidiVinci = false;
	//Hurray !
	
	//L'ID de l'arr�t sur lequel on est actuellement (au sortir d'un move)
	public String IDArretActuel = "";
	public Date HeureActuelle = null;
	public String NomArrivee;
	public Boolean Success = false;
	public String Score = "";

	
	public List<Incident> incidents = new ArrayList<Incident>();
	
	
	//L'�num�ration pour les actions possibles en call REST.
	public static enum GameType{training, arena};
	public static enum ActionType{connect, check, move, incidents};	
	
	//Le dictionnaire qui recence les requ�tes disponibles.
	private Map<ActionType, String> dicoURL = new HashMap<ActionType, String>();
	
	/**
	 * Instancie un objet Vide. Pouorquoi ? Comme �a, pour voir.
	 */
	public DialogManager() {	}
	
	/**
	 * Instancie un objet Vide. Pourquoi ? Comme �a, pour voir.
	 * @param _botToken   	 Identifiant unique du bot � fair bouger.
	 */
	public DialogManager(String _botToken, String _botSecret)
	{
		this.botToken = _botToken;
		this.botSecret = _botSecret;
		dicoURL.put(ActionType.connect, "http://24hc15.haum.org/api/connect/" + this.botToken);
	}
	
	/**
	 * Demande au serveur distant de rejoindre une partie dans le mode pass� en param�tres.
	 * @param gameType	Le type de jeu souhait� : Training ou Arena.
	 * @returns 		Un bool�en indiquant si l'op�ration s'est bien pass�e ou pas.
	 */
	@SuppressWarnings("static-access")
	public Boolean JoinGame(GameType gameType)
	{
		Boolean ok = true;
		try
		{			
			//Construction du json qui demande � joindre une partie.
			String type = "arena";
			if (gameType == gameType.training) { type = "training"; }
			
			//Construction de la requ�te JSON � passer dans la requ�te Http. 
			String json = new StringBuilder()
				.append("{\"secret\": \"")
				.append(this.botSecret)
				.append("\",\"mode\": \"")
				.append(type).append("\"}")
				.toString();			
			
			String erreur = ExecuteRequest(ActionType.connect, json);
			if (erreur != null && erreur != "") { throw new Exception(erreur); }
			
			json = new StringBuilder()
				.append("{\"secret_token\": \"")
				.append(this.botSecret)
				.append("\"}")
				.toString();
			
			erreur = ExecuteRequest(ActionType.check, json);
			if (erreur != null && erreur != "") { throw new Exception(erreur); }
		}
		catch(Exception e)
		{
			Erreur_Detectee = true;
			Erreur = e.getMessage();
			ok = false;
		}
		return ok;
	}
	
	public Boolean CheckGameStart()
	{
		Boolean ok = true;
		try
		{			
			//Construction de la requ�te JSON � passer dans la requ�te Http. 
			String json = new StringBuilder()
			.append("{\"secret_token\": \"")
			.append(this.botSecret)
			.append("\"}")
			.toString();			
			
			String erreur = ExecuteRequest(ActionType.check, json);
			if (erreur != null && erreur != "") { throw new Exception(erreur); }			
		}
		catch(Exception e)
		{
			Erreur_Detectee = true;
			Erreur = e.getMessage();
			ok = false;
		}
		return ok;
	}
	
	public Boolean MoveBot(String idLigne, String idArret, Date correspondance)
	{
		Boolean ok = true;
		try
		{			
			//Construction de la requ�te JSON � passer dans la requ�te Http.
			SimpleDateFormat formatter = new SimpleDateFormat("dd MMM HH:mm:ss yyyy", Locale.ENGLISH);			
			String json = new StringBuilder()
				.append("{\"secret_token\": \"")
				.append(this.botSecret)
				.append("\",\"track\": \"")
				.append(idLigne)
				.append("\",\"connection\": \"")
				.append(formatter.format(correspondance))
				.append("\",\"to_stop\": \"")
				.append(idArret)
				.append("\",\"type\": \"move\"}")
				.toString();
			
			String erreur = ExecuteRequest(ActionType.move, json);
			if (erreur != null && erreur != "") { throw new Exception(erreur); }			
		}
		catch(Exception e)
		{
			Erreur_Detectee = true;
			Erreur = e.getMessage();
			ok = false;
		}
		return ok;		
	}
	
	public Boolean GetIncidents()
	{
		Boolean ok = true;
		try
		{			
			//Construction de la requ�te JSON � passer dans la requ�te Http.			
			String json = "";
			
			String erreur = ExecuteGetRequest(ActionType.incidents);
			if (erreur != null && erreur != "") { throw new Exception(erreur); }			
		}
		catch(Exception e)
		{
			Erreur_Detectee = true;
			Erreur = e.getMessage();
			ok = false;
		}
		return ok;		
	}
	
	/**
	 * Demande au server distant une r�ponse � un appel dont la teneur est pass�e en param�tres.
	 * @param _postCOntent	Les param�tres json � passer au serveur.
	 * @returns Le contenu de la r�ponse du serveur, ou null si p�pin.
	 */
	public String ExecuteRequest(ActionType action, String json)
	{
		String retour = null;
		
		try {
			
			if (dicoURL.containsKey(action)){
			
				//On construit la requ�te et la connexion d'apr�s le type d'action souhait�
				String url = dicoURL.get(action);				
				
				 try(CloseableHttpClient httpClient = HttpClientBuilder.create().build())
				 {
					 HttpPost request = new HttpPost(url);
		             StringEntity params = new StringEntity(json);
		             request.addHeader("content-type", "application/json");
		             request.setEntity(params);
		             HttpResponse result = httpClient.execute(request);

		             ParseJsonResponse(EntityUtils.toString(result.getEntity(), "UTF-8"), action);
		        } 
			 	catch (IOException ex) 
			 	{
			 		retour = new StringBuilder().append("Http Client Exception : ").append(ex.getMessage()).toString();
			 	}
			}
			else
			{
				throw new Exception("Requ�te inconnue pour l'action choisie.");
			}
		}
		catch (Exception exc) 
		{
			retour = new StringBuilder().append("General Exception : ").append(exc.getMessage()).toString();	 
		}
		return retour;
	}
	
	public String ExecuteGetRequest(ActionType action)
	{
		String retour = null;
		
		try {
			
			if (dicoURL.containsKey(action)){
			
				//On construit la requ�te et la connexion d'apr�s le type d'action souhait�
				String url = dicoURL.get(action);				
				
				 try(CloseableHttpClient httpClient = HttpClientBuilder.create().build())
				 {
					 HttpGet request = new HttpGet(url);
		             request.addHeader("content-type", "application/json");
		             HttpResponse result = httpClient.execute(request);

		             ParseJsonResponse(EntityUtils.toString(result.getEntity(), "UTF-8"), action);
		        } 
			 	catch (IOException ex) 
			 	{
			 		retour = new StringBuilder().append("Http Client Exception : ").append(ex.getMessage()).toString();
			 	}
			}
			else
			{
				throw new Exception("Requ�te inconnue pour l'action choisie.");
			}
		}
		catch (Exception exc) 
		{
			retour = new StringBuilder().append("General Exception : ").append(exc.getMessage()).toString();	 
		}
		return retour;
	}
	
	
	private String ParseJsonResponse(String toParse, ActionType action)
	{
		try 
        {
       	 	JSONParser parser = new JSONParser();
            Object resultObject = parser.parse(toParse);
            String affichageLog = "";

            if (resultObject instanceof JSONArray) 
            {
                JSONArray array=(JSONArray)resultObject;
                for (Object object : array) 
                {
                    JSONObject obj = (JSONObject)object;
                    System.out.println(obj.get("success"));
                    System.out.println(obj.get("fr"));
                }
            }
            else if (resultObject instanceof JSONObject) 
            {
            	JSONObject obj =(JSONObject)resultObject;
               
            	switch(action)
            	{
            	case connect:
            		String resultat_connect = "";
            		if (!Boolean.parseBoolean(obj.get("success").toString()))
            		{            
            			resultat_connect = "KO";
            			throw new Exception("Param�tre Success � False !");
            		}
            		String url = new StringBuilder().append("http://24hc15.haum.org").append(obj.get("url").toString()).toString();
            		dicoURL.put(ActionType.check, url);            		
            		this.gameToken = (url.substring(url.indexOf("/play/") + 6));
            		this.gameToken = this.gameToken.substring(0, this.gameToken.indexOf('/'));
            		dicoURL.put(ActionType.move, new StringBuilder().append("http://24hc15.haum.org/api/play/").append(this.gameToken).append("/").append(this.botToken).append("/move").toString());
            		dicoURL.put(ActionType.incidents, new StringBuilder().append("http://24hc15.haum.org/api/incidents/").append(this.gameToken).toString());
            		resultat_connect = "OK";
            		Horodatage connect = new Horodatage(new StringBuilder().append("Connexion au serveur : ").append(resultat_connect).toString());
            		affichageLog = new StringBuilder().append(affichageLog).append("\n").append(connect.getMessageHorodatage()).toString();
        			break;
        		
            	case check:
            		String resultat_check = "";
            		if (!Boolean.parseBoolean(obj.get("success").toString()))
            		{      
            			resultat_check = "KO";
            			throw new Exception("Param�tre Success � False !");
            		}
            		String gameState = obj.get("status").toString();
            		if (gameState.equals("game_started"))
            		{
            			this.GameStarted = true;
            			this.TimeRemainingBeforeGameStart = 0;
            			this.IdArretDepart =  ((JSONObject)obj.get("first_stop")).get("id").toString();            			
            			this.IdArretDestination = ((JSONObject)obj.get("target")).get("id").toString();            			
            			SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'");
            			String dtDep = obj.get("dtstart").toString();
            			this.DateDepart = formatter.parse(dtDep);
            			resultat_check = new StringBuilder().append("OK. Arret de départ : ").append(this.IdArretDepart).append(" - Arret de destination : ").append(this.IdArretDestination).append(" - Date de départ : ").append(this.DateDepart).toString();
            		}
            		else
            		{
            			this.GameStarted = false;
            			this.TimeRemainingBeforeGameStart = Integer.parseInt(obj.get("time").toString());
            			resultat_check = new StringBuilder().append("en cours. Temps restant avant le démarrage du jeu : ").append(this.TimeRemainingBeforeGameStart).toString();
            		}
            		Horodatage check = new Horodatage(new StringBuilder().append("Démarrage du jeu : ").append(resultat_check).toString());
            		affichageLog = new StringBuilder().append(affichageLog).append("\n").append(check.getMessageHorodatage()).toString();
        			break;
        		
            	case move:
            		String resultat_move = "";
            		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'");
            		String heure = "";
            		//Si le mouvement s'est bien effectu�
            		if (Boolean.parseBoolean(obj.get("success").toString()))
            		{
            			String status = obj.get("status").toString();
            			this.ErreurMove = false;
                		switch (status)
                		{
                		case "moved":            			
                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();                			
                			heure = obj.get("time").toString();
                			this.HeureActuelle = formatter.parse(heure);
                			resultat_move = new StringBuilder().append("OK. Le bot s'est déplacé. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
                			break;
                			
                		case "rerouted":
                			this.Rerouted = true;
                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
                			this.IdArretDestination = ((JSONObject)obj.get("target")).get("id").toString();
                			heure = obj.get("time").toString();
                			this.HeureActuelle = formatter.parse(heure);
                			resultat_move = new StringBuilder().append("OK. Le bot est rerouté. Arret actuel : ").append(this.IDArretActuel).append(" - Arret de destination : ").append(this.IdArretDestination).append(" - Date : ").append(this.HeureActuelle).toString();
                			break;
                			
                		case "arrived":
                			this.VeniVidiVinci = true;
                			this.Score = obj.get("time").toString();
                			resultat_move="OK. Le bot est arrivé. Veni Vidi Vinci. Score : "+this.Score;
                			resultat_move = new StringBuilder().append("OK. Le bot est arrivé. Veni Vidi Vinci. Score : ").append(this.Score).toString();
                			break;
                		}            			
            		}
            		else
            		{
            			this.ErreurMove = true;
            			String status = obj.get("status").toString();
                		switch (status)
                		{
	                		case "unknown_track":
	                			this.LigneInconnue = true;
	                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
	                			heure = obj.get("time").toString();
	                			this.HeureActuelle = formatter.parse(heure);
	                			resultat_move = new StringBuilder().append("KO. Ligne inconnue. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
	                			break;
	                		case "bad_stop":
	                			this.ArretPasSurLigne = true;
	                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
	                			heure = obj.get("time").toString();
	                			this.HeureActuelle = formatter.parse(heure);
	                			resultat_move = new StringBuilder().append("KO. L'arrêt n'existe pas sur la ligne. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
	                			break;
	                		case "bad_track":
	                			this.LigneNonAccessible = true;
	                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
	                			heure = obj.get("time").toString();
	                			this.HeureActuelle = formatter.parse(heure);
	                			resultat_move = new StringBuilder().append("KO. La ligne n'est pas accessible. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
	                			break;
	                		case "connection_date_not_valid":
	                			this.HoraireMalFormate = true;
	                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
	                			heure = obj.get("time").toString();
	                			this.HeureActuelle = formatter.parse(heure);
	                			resultat_move = new StringBuilder().append("KO. Mauvais format de date. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
	                			break;
	                		case "unknown_connection":
	                			this.HoraireInconnu = true;
	                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
	                			heure = obj.get("time").toString();
	                			this.HeureActuelle = formatter.parse(heure);
	                			resultat_move = new StringBuilder().append("KO. Horaire de passage non valide. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
	                			break;
	                		case "pasted_connection":
	                			this.HoraireDepasse = true;
	                			this.IDArretActuel = ((JSONObject)obj.get("stop")).get("id").toString();
	                			heure = obj.get("time").toString();
	                			this.HeureActuelle = formatter.parse(heure);
	                			resultat_move = new StringBuilder().append("KO. Horaire de passage dépassé. Arret actuel : ").append(this.IDArretActuel).append(" - Date : ").append(this.HeureActuelle).toString();
	                			break;                			
                		}
            		}       
            		Horodatage move = new Horodatage(new StringBuilder().append("Déplacement du bot : ").append(resultat_move).toString());
            		affichageLog = new StringBuilder().append(affichageLog).append("\n").append(move.getMessageHorodatage()).toString();
        			break;
        		
            	case incidents:
            		if (!Boolean.parseBoolean(obj.get("success").toString()))
            		{            			
            			throw new Exception("Param�tre Success � False !");
            		}
            		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'");
            		JSONArray array = (JSONArray) obj.get("incidents");
            		Iterator i = array.iterator();
            		
            		while (i.hasNext())
            		{
            			JSONObject ob = (JSONObject)i.next();
            			Incident inc = new Incident( 
            					Integer.parseInt(ob.get("penalty").toString()),
            					new Arret(
            							((JSONObject)ob.get("stop")).get("name").toString(),
            							Integer.parseInt(((JSONObject)ob.get("stop")).get("id").toString())
            							),
    							format.parse(ob.get("dtend").toString()),
    							format.parse(ob.get("dtstart").toString()),
    							ob.get("type").toString()            					
            					);
            			this.incidents.add(inc);
            		}            		
        			break;            	   
            	}
            	
        	    // Conservation des messages dans un fichier log :
        	    
    	  		String adresseDuFichier = System.getProperty("user.dir") + "/lancement_urban_flow.log";

    	  		try
    	  		{
    	  			/**
    	  			 * BufferedWriter a besoin d un FileWriter, 
    	  			 * les 2 vont ensemble, on donne comme argument le nom du fichier
    	  			 * true signifie qu on ajoute dans le fichier (append), on ne marque pas par dessus 
    	  			 
    	  			 */
    	  			FileWriter fw = new FileWriter(adresseDuFichier, true);
    	  			
    	  			// le BufferedWriter output auquel on donne comme argument le FileWriter fw cree juste au dessus
    	  			BufferedWriter output = new BufferedWriter(fw);
    	  			
    	  			//on marque dans le fichier ou plutot dans le BufferedWriter qui sert comme un tampon(stream)
    	  			output.write(affichageLog);
    	  			//on peut utiliser plusieurs fois methode write
    	  			
    	  			output.flush();
    	  			//ensuite flush envoie dans le fichier, ne pas oublier cette methode pour le BufferedWriter
    	  			
    	  			output.close();
    	  			//et on le ferme
    	  		}
    	  		catch(IOException ioe){
    	  			System.out.print("Erreur : ");
    	  			ioe.printStackTrace();
    	  			}
            	
            	
            }
        } 
		catch (Exception e) 
		{
			return new StringBuilder().append("JSON Parser Exception : ").append(e.getMessage()).toString();
		}
		return null;				
	}
	
	
}
